/*
 * Systemprogrammierung
 * Multiplayer-Quiz
 *
 * Client
 *
 * main.c: Hauptprogramm des Clients
 */

#include "common/sockets.h"
#include "main.h"
#include "listener.h"
#include "fragewechsel.h"
#include "gui_thread.h"


/*******Client-ID Daten*******/

static uint8_t client_id;

/* Liefert ClientId */
int get_client_id() { return client_id; }


/*******Socket Daten*******/

static int sock;

/* Liefert Server Socket */
int get_socket() { return sock; }


/*******Main Argumente*******/

static int argc_main;
static char **argv_main;

/** Liefert Argumente Count */
int get_main_argc() { return argc_main; }

/* Liefert Argumente Array */
char** get_main_argv() { return argv_main; }


/*******Thread-Daten*******/

static pthread_t thread_gui;

/* Liefert GUI Thread ID */
pthread_t get_gui_thread() { return thread_gui; }



/* 	Sendet Login-Request
 *  an Server und wartet
 * 	auf Login-Response-OK */
uint8_t login(char *name)
{
	/*******Sendet Login-Anfrage an Server*******/
	if (catch_com_err(send_MSG(sock, LOGIN_REQUEST, strlen(name), name)) == COM_ERR)
	{
		return 255;
	}
	/*******Daten für Login-OK*******/

	// MSG-Struktur für Nachrichtenempfang
	struct RFC_MSG login_response;

	// Daten für Error-Behandlung
	uint8_t subtype = 0;
	char* err_msg;

	/*******Empfange Login-OK*******/

	switch (catch_com_err(receive_MSG(sock, &login_response)))
	{

		/*******Login-OK erhalten*******/

		case LOGIN_RESPONSE_OK:

			// Kopiere Daten des Void-Pointer in Zahl
			memcpy(&client_id, login_response.data, login_response.header.length);


			// Rückkehr mit Client-ID
			return (client_id);

			/*******Error empfangen*******/

		case ERROR_WARNING:

			// Hole Speicher für Error-Msg
			err_msg = (char*) malloc(login_response.header.length + sizeof(uint8_t));

			// err_msg mit Nullen füllen (automatische Ende-Markierung)
			memset(err_msg, 0, login_response.header.length + sizeof(uint8_t));

			// Kopiere Subtyp und Fehlerbeschreibung aus Error-MSG
			memcpy(&subtype, login_response.data, sizeof(uint8_t));
			memcpy(err_msg, login_response.data + sizeof(uint8_t), login_response.header.length - sizeof(uint8_t));

			// Unterscheide Error-Typ
			switch (subtype)
			{

				// Wenn Error-Typ Warnung
				case WARNING:
					infoPrint("%s\n", err_msg);

					// Freigeben des Speichers
					free(err_msg);
					err_msg = NULL;
					break;

					// Wenn Error-Typ Fatal
				case FATAL:
					infoPrint("%s\n", err_msg);
					return (255);

				default:
					infoPrint("Unbekannter Fehler ist im Server aufgetreten\n");
					break;
			}
			break;

		case COM_ERR:
			return (255);

		default:
			break;
	}

	// Bei fehlgeschlagenem Login
	return (255);
}

/* Durchläuft die Argumente und kontrolliert sie
 * auf Gültigkeit */
void parse_commands(char** args)
{
	// opt = gerade geladene Option, option_count = zähle wichtige Optionen
	int opt, option_count = 0;

	// Solange Optionsindex kleiner als Anzahl der Optionen
	while (optind < argc_main)
	{
		// Hole Option an Stelle optin (nur n, h und p), optind wird um eins erhöht
		opt = getopt(argc_main, argv_main, "n:h:p:d");

		// Keine Argumente mehr vorhanden
		if (opt == -1)
		{
			break;
		}

		switch (opt)
		{
			// Wenn kein Argument für die Option angegeben wurde
			case ':':
				infoPrint("Client beendet - fehlende Argumente...\n");
				infoPrint("client -n NAME -h SERVER -p PORT [-d (DebugMode)]\n");
				infoPrint("Beispiel: ./client -n Spieler -h 127.0.0.1 -p 8000\n");
				exit(0);
				break;

				// Option name
			case 'n':
				// Argument von Option n in args[0]
				args[0] = optarg;
				// wenn Name länger als 31 Zeichen, breche mit Hinweis auf maximale Länge ab
				if (strlen(args[0]) > 31) {
					infoPrint("Eingegebener Name zu lang - maximal 31 Zeichen erlaubt\n");
					infoPrint("Beende...\n");
					exit(0);
				}
				option_count++;
				break;

				// Option host
			case 'h':
				// Argument von Option h in args[1]
				args[1] = optarg;
				option_count++;
				break;

				// Option port
			case 'p':
				// Argument von Option p in args[2]
				args[2] = optarg;
				option_count++;
				break;

				// Option debugMode
			case 'd':
				// Debug Modus an
				debugEnable();
				break;

				// Option nicht bekannt
			case '?':
				infoPrint("Option nicht bekannt\n");
				break;

			default:
				break;
		}
	}

	// Wenn weniger als 3 Optionen eingegeben wurde
	if (option_count < 3)
	{
		infoPrint("Client beendet - fehlende Argumente...\n");
		infoPrint("client [-n NAME] [-h SERVER] [-p PORT]\n");
		infoPrint("Beispiel: ./client -n Spieler -h 127.0.0.1 -p 8000\n");
		exit(0);
	}

	return;
}

/*	Hauptprogramm des Clients
 * 	Verbindung zum Server über socket aufbauen
 * 	Login Versuch Starten
 * 	Threads Starten */
int main(int argc, char **argv)
{
	setProgName(argv[0]);

	/*******Start Parameter einlesen*******/

	// Parameter in Globalen Variablen ablegen
	argc_main = argc;
	argv_main = argv;

	// Hier werden die Argumente gespeichert
	char* args[3];

	parse_commands(args);

	// Ausgabe Client-Version
	infoPrint("Client Gruppe 09\n");

	/*******Versuche Socket Verbindung zu Server aufzubauen*******/

	// Verbindung zu Server aufbauen über Socket "sock"
	sock = create_client_socket(args);

	// Fange Fehler bei der Socket-Erstellung und Verbindungsaufbau ab
	if (sock == -1)
	{
		perror("Verbindung konnte nicht aufgebaut werden: ");
		exit(0);
	}

	/*******Versuche Login*******/

	// Login-Prozedur bei erfolgreich verbundenem Socket
	client_id = login(args[0]);

	// Fange Fehler beim Login ab
	if (client_id == 255)
	{
		infoPrint("Login gescheitert... Beende...\n");
		exit(0);
	}

	/*******Login OK - starte Threads*******/

	// Semaphor für GUI-Thread, damit Listener-Thread keine Funktionen der GUI aufrufen kann, bevor diese Initialisiert wurde
	sem_init(&gui_thread_init, 0, 0);

	// GUI-Thread erzeugen und Fehler beim erzeugen abfangen
	if (pthread_create(&thread_gui, NULL, gui_thread, NULL) != 0)
	{
		debugPrint("GUI-Thread Error\n");
		return 0;
	}

	pthread_t thread_listener;
	// Listener-Thread erzeugen
	if (pthread_create(&thread_listener, NULL, listener_thread, NULL) != 0)
	{
		debugPrint("Listener-Thread Error\n");
		return 0;
	}

	pthread_t thread_question;
	// Question-Thread erzeugen
	if (pthread_create(&thread_question, NULL, question_thread, NULL) != 0)
	{
		debugPrint("Fragewechsel-Thread Error\n");
		return 0;
	}


	/*******Warte auf Ende*******/


	pthread_join(thread_listener, NULL);
	close(sock);
	sem_post(&question);
	pthread_join(thread_question, NULL);

	return 0;
}
